#include"../../inc/task/MyTask.h"
using std::make_pair;

namespace SE
{
    MyTask::MyTask(const string &stopWords_dir, const string &ripePage_dir, const string &offset_dir, const string &invertIndex_dir)
    : _stopWords_dir(stopWords_dir), _ripePage_dir(ripePage_dir), _offset_dir(offset_dir), _invertIndex_dir(invertIndex_dir)
    {
        readInfoFromFile();
    }

    void MyTask::readInfoFromFile()
    {
        ifstream stopwords(_stopWords_dir);
        if(!stopwords.good())
        {
            logError("Open File");
            exit(1);
        }
        string words;
        while(getline(stopwords, words))
        {
            _stopWords.insert(words);
        }

        ifstream pages(_ripePage_dir);
        ifstream offset(_offset_dir);
        ifstream index(_invertIndex_dir);
        if(!pages.good() || !offset.good() || !index.good())
        {
            logError("Open File");
            exit(1);
        }

        {
            string s;
            int id;
            off_t begin, end;
            while (getline(offset, s))
            {
                stringstream ss(s);
                ss >> id >> begin >> end;
                char buf[BUFSIZ];
                pages.read(buf, end - begin);
                string doc(buf);
                Online_WebPage page(doc, _splitTool, _stopWords);

                _offsetLib[id] = make_pair(begin, end);
                _webPages.push_back(std::move(page));
            }
        }

        {
            string s;
            string word;
            int id;
            double weight;
            while(getline(index, s))
            {
                stringstream ss(s);
                ss >> word;
                while (ss)
                {
                    ss >> id >> weight;
                    _invertIndexTable[word][id] = weight;
                }
            }
        }

        stopwords.close();
        offset.close();
        pages.close();
        index.close();
        logInfo("停词库，网页库，偏移库，倒排索引库读取数据成功!");
    }

    void MyTask::run(const string &sentence, const TcpConnectionPtr &ptr)
    {
        Online_WebPage page(sentence, _splitTool, _stopWords);

        map<string, int>& wordsmap = page.getWordsMap();
        vector<pair<string, double>> weightVec = getWeightVector(wordsmap);

        set<int> pageIDs = getAllMatchingPages(weightVec);
        string ret;
        if(pageIDs.empty())
            ret = return404Json();
        else 
        {
            vector<pair<int, double>> docVec;
            docVec = cosSort(weightVec, pageIDs);
            makePageSummary(pageIDs, wordsmap);
            ret = JsonPackage(docVec);
        }

        int size = ret.size();
        string msg(std::to_string(size));
        msg.append("\n")
           .append(ret);
        ptr->sendInLoop(msg);
    }

    vector<pair<string, double>> MyTask::getWeightVector(const map<string, int>& wordsmap)
    {
        int sum = 0;
        for(auto & k : wordsmap)
            sum += k.second;
        
        vector<pair<string, double>> vec;
        for(auto & k : wordsmap)
        {
            string word = k.first;
            double TF = static_cast<double>(k.second/ sum);
            int DF = _invertIndexTable[word].size();
            int num = _webPages.size();
            double IDF = log10(static_cast<double>(num/ (DF+1)));
            double weight = TF * IDF;
            vec.push_back(make_pair(word, weight));
        }

        double sum1 = 0;
        for(auto & k : vec)
            sum1 += k.second * k.second;
        sum1 = sqrt(sum1);
        for(auto & k : vec)
            k.second /= sum;
        
        return vec;
    }

    set<int> MyTask::getAllMatchingPages(const vector<pair<string, double>>& vec)
    {
        vector<set<int>> id_set;
        for(size_t i = 0; i < vec.size(); i++)
        {
            string word = vec[i].first;
            unordered_map<int, double> temp = _invertIndexTable[word];
            set<int> sets;
            for(auto & k : temp)
                sets.insert(k.first);
            id_set.push_back(sets);
        }

        set<int> res = id_set[0];
        for(size_t i = 1; i < id_set.size(); i++)
        {
            set<int> temp;
            std::set_intersection(id_set[i].begin(), id_set[i].end(), res.begin(), res.end(), std::inserter(temp, temp.begin()));
            swap(res, temp);
        }

        return res;
    }

    vector<pair<int, double>> MyTask::cosSort(const vector<pair<string, double>>& vec, const set<int>& IDs)
    {
        vector<double> weightVec;
        for(auto & k : vec)
            weightVec.push_back(k.second);

        map<int, vector<double>> weightsMap; // map<docID, weightVec>
        for(auto & id : IDs)
        {
            for(auto & k : vec)
            {
                string word = k.first;
                double weight = _invertIndexTable[word][id];
                weightsMap[id].push_back(weight);
            }
        }

        vector<pair<int, double>> sortedVec; // vector<pair<docID, cos>>
        for(auto & k : weightsMap)
        {
            int id = k.first;
            vector<double> pageVec = k.second;
            sortedVec.push_back(make_pair(id, std::move(cos(pageVec, weightVec))));
        }

        std::sort(sortedVec.begin(), sortedVec.end(), [&](const pair<int, double>& lhs, const pair<int, double>& rhs){
            return lhs.second > rhs.second;
        });

        return sortedVec;
    }

    double MyTask::cos(const vector<double>& X, const vector<double>& Y)
    {
        if(X.size() != Y.size())
        {
            logError("Vector Size Doesnt Match");
            exit(1);
        }

        double up = 0;
        for(size_t i = 0; i < X.size(); i++)
            up += X[i] * Y[i];

        double down = mod(X) * mod(Y);
        return up/ down;
    }

    double MyTask::mod(const vector<double>& vec)
    {
        int size = vec.size();
        double sum = 0.0;
        for(int i = 0; i < size; i++)
            sum += vec[i] * vec[i];
        return sqrt(sum);
    }

    void MyTask::makePageSummary(const set<int>& pageIds, const map<string,int>& wordsMap)
    {
        for(auto & k : pageIds)
            _webPages[k].makeSummary(wordsMap);
    }

    string MyTask::return404Json()
    {
        Json::Value root, array, elem;

        elem["title"] = "404 NOT FOUND";
        elem["summary"] = "没有找到相关的网页";
        elem["url"] = "nullptr";
        array.append(elem);

        root["files"] = array;

        return root.toStyledString();
    }

    string MyTask::JsonPackage(vector<pair<int, double>>& sortedVec)
    {
        Json::Value root;
        Json::Value file;
        int count = 0;
        for(auto & k : sortedVec)
        {
            int id = k.first;
            Online_WebPage webpage = _webPages[id];

            Json::Value elem;
            elem["title"] = webpage.getTitle();
            elem["summary"] = webpage.getSummary();
            elem["url"] = webpage.getLink();
            
            file.append(elem);
            if(++count == 100)
                break;
        }
        root["files"] = file;
        
        Json::StyledWriter writer;
        return writer.write(root);
    }
}